#include "vsphere_handle.h"
#include <vector>
#include <algorithm>
#include <map>
#include <syslog.h>
#include "util.h"

const int rdie_size = sizeof(struct runtime_disk_info_encoding);
const int rmde_size = sizeof(struct runtime_meta_data_encoding);
const int rce_size = sizeof(struct runtime_cbt_encoding);


void process_json_work_file(const char *json_work_file, json_t **json_config)
{
    json_error_t error;

    *json_config = json_load_file(json_work_file, JSON_DECODE_ANY, &error);
    if (!*json_config)
    {
        fprintf(stderr,
                "Failed to parse JSON config %s [%s at line %d column %d]\n",
                json_work_file, error.text, error.line, error.column);
        syslog(LOG_ERR, "Failed to parse JSON config %s [%s at line %d column %d]", json_work_file, error.text, error.line, error.column);
        exit(1);
    }
}

std::vector<DataSegment> get_cbtinfo(json_t *json_config)
{
    std::vector<DataSegment> result;
    json_t *DiskChangeInfo = json_object_get(json_config, CBT_DISKCHANGEINFO_KEY);
    json_t *changedArea = json_object_get(DiskChangeInfo, CBT_CHANGEDAREA_KEY);
    size_t index;
    json_t *array_element;
    json_t *start, *length;
    json_array_foreach(changedArea, index, array_element)
    {
        start = json_object_get(array_element, CBT_CHANGEDAREA_START_KEY);
        length = json_object_get(array_element, CBT_CHANGEDAREA_LENGTH_KEY);
        if (!start || !length)
        {
            continue;
        }
        off_t start_offset = json_integer_value(start);
        off_t length_offset = json_integer_value(length);
        result.push_back({start_offset, start_offset + length_offset});
        //syslog(LOG_DEBUG, "found data segment: %ld - %ld", start_offset, start_offset + length_offset);
    }
    return result;
}

off_t get_length(json_t *json_config)
{
    json_t *DiskChangeInfo = json_object_get(json_config, CBT_DISKCHANGEINFO_KEY);
    json_t *length = json_object_get(DiskChangeInfo, CBT_DISK_SIZE);
    off_t len = json_integer_value(length);
    return len;
}

bool find_data_region(int fd, off_t offset, off_t range_end, off_t& start, off_t& end) {
    start = lseek(fd, offset, SEEK_DATA);
    if (start == static_cast<off_t>(-1)) {
        if (errno == ENXIO) {
            return false;
        } else {
            syslog(LOG_ERR, "lseek SEEK_DATA failed at offset %lld: %s", static_cast<long long>(offset), strerror(errno));
            return false;
        }
    }
    if (start >= range_end) {
        return false;
    }
    end = lseek(fd, start, SEEK_HOLE);
    if (end == static_cast<off_t>(-1)) {
        if (errno == ENXIO) {
            end = lseek(fd, 0, SEEK_END);
            if (end == static_cast<off_t>(-1)) {
                syslog(LOG_ERR, "lseek SEEK_END failed: %s", strerror(errno));
                return false;
            }
        } else {
            syslog(LOG_ERR, "lseek SEEK_HOLE failed at offset %lld: %s", static_cast<long long>(start), strerror(errno));
            return false;
        }
    }
    if (end > range_end) {
        end = range_end;
    }
    return start < end;
}

std::vector<DataSegment> get_segments(std::vector<DataSegment> segments, off_t range_start, off_t range_end) {
    std::vector<DataSegment> result;
    for (auto &seg : segments) {
        if (seg.start >= range_end) break;
        if (seg.end <= range_start) continue;
        result.push_back({
            std::max(seg.start, range_start),
            std::min(seg.end,   range_end)
        });
    }
    return result;
}

void read_vsphere_disk_data(std::vector<int> &fd_vector, char *buf, off_t offset, size_t rsize, const std::unordered_map<int, std::vector<DataSegment>>& cache)
{
    //syslog(LOG_ERR, "[%ld]read_vsphere_disk_data:at offset %lu size %lu, fd_vector size = %lu\n",usecTime(), offset, rsize, fd_vector.size());
    ssize_t n;
    if (fd_vector.empty())
    {
        memset(buf, 0, rsize);
        return;
    }

    if(fd_vector.size() == 1)
    {
        n = pread(fd_vector[0], buf, rsize, offset);
        //syslog(LOG_ERR, "[%ld]read_vsphere_disk_data:full pread n = %ld, offset = %lu, rsize = %ld\n",usecTime(), n, offset, rsize);
        if (n < 0) {
            syslog(LOG_ERR, "pread failed for single fd: %s", strerror(errno));
            memset(buf, 0, rsize);
        }
        return;
    }

    n = pread(fd_vector[0], buf, rsize, offset);
    //syslog(LOG_ERR, "[%ld]read_vsphere_disk_data:incr first pread n = %ld, offset = %lu, rsize = %ld\n",usecTime(), n, offset, rsize);
    if (n < 0) {
        syslog(LOG_ERR, "pread failed for single fd: %s", strerror(errno));
        memset(buf, 0, rsize);
    }

    off_t end_offset = offset + static_cast<off_t>(rsize);
    for (size_t i = 1 ; i < fd_vector.size(); i++) {
        int fd = fd_vector[i];
        auto it = cache.find(fd);
        if (it == cache.end()) {
            syslog(LOG_ERR, "cache not found for fd[%d], index = %lu\n", fd, i);
            continue;
        }
        auto segments = get_segments(it->second, offset, end_offset);
        for (auto &seg : segments) {
            size_t buf_pos = static_cast<size_t>(seg.start - offset);
            size_t read_size = static_cast<size_t>(seg.end - seg.start);
            if (read_size > 0) {
                n = pread(fd, buf + buf_pos, read_size, seg.start);
                //syslog(LOG_ERR, "[%ld]read_vsphere_disk_data:incr pread n = %ld, offset = %lu, rsize = %ld\n",usecTime(), n, seg.start, read_size);
                if (n < 0) {
                    syslog(LOG_ERR, "pread failed for fd[%lu] at %lld: %s",
                           i, static_cast<long long>(seg.start), strerror(errno));
                }
            }
        }
    }
    //syslog(LOG_ERR, "[%ld]read_vsphere_disk_data:end\n",usecTime());

    /*
    //printf("rdie_size = %d, rmde_size = %d, rce_size = %d\n", rdie_size, rmde_size, rce_size);
    printf("offset = %ld, rsize = %ld\n", offset, rsize);
    // lseek(fd, rdie_size + rmde_size, SEEK_SET);
    lseek(fd, 0, SEEK_SET);
    struct runtime_disk_info_encoding rdie;
    memset(&rdie, 0, rdie_size);
    if (read(fd, (char *)&rdie, rdie_size) != rdie_size)
    {
        fprintf(stderr, "%d:vsphere disk read error\n", __LINE__);
        exit(1);
    }
    //printf("rdie.start_magic = %x, rdie.end_magic = %x\n", rdie.start_magic, rdie.end_magic);

    struct runtime_meta_data_encoding rmde;
    memset(&rmde, 0, rmde_size);
    //char *key = NULL;
    //char *buffer = NULL;
    while (1)
    {
        if (read(fd, (char *)&rmde, rmde_size) != rmde_size)
        {
            fprintf(stderr, "%d:vsphere disk read error\n", __LINE__);
            exit(1);
        }
        //printf("rmde.start_magic = %x, rmde.end_magic = %x\n", rmde.start_magic, rmde.end_magic);
        //printf("rmde.meta_key_length = %d, rmde.meta_data_length = %d\n", rmde.meta_key_length, rmde.meta_data_length);
        if (rmde.meta_key_length == 0 && rmde.meta_data_length == 0)
        {
            break;
        }
        //key = (char *)malloc(rmde.meta_key_length);
        //buffer = (char *)malloc(rmde.meta_data_length);
        //read(fd, key, rmde.meta_key_length);
        //read(fd, buffer, rmde.meta_data_length);
        //printf("Meta data key %s, value %s\n", key, buffer);

        //free(key);
        //free(buffer);
        lseek(fd, rmde.meta_key_length + rmde.meta_data_length, SEEK_CUR);
    }
    struct runtime_cbt_encoding rce;
    memset(&rce, 0, rce_size);
    size_t totle_read_size = 0;
    while (read(fd, (char *)&rce, rce_size) == rce_size)
    {
        //printf("rce.start_magic = %x, rce.end_magic = %x\n", rce.start_magic, rce.end_magic);
        //printf("rce.start_offset = %lu, rce.offset_length = %lu\n", rce.start_offset, rce.offset_length);
        if (rce.start_offset + rce.offset_length < (uint64_t)offset)
        {
            lseek(fd, (int64_t)rce.offset_length, SEEK_CUR);
            continue;
        }
        if (rce.start_offset > (uint64_t)offset + rsize)
        {
            break;
        }
        size_t read_size = 0;
        off_t read_offset = 0;
        printf("rce.start_offset = %lu, rce.offset_length = %lu\n", rce.start_offset, rce.offset_length);
        if (rce.start_offset < (uint64_t)offset)
        {
            read_size = (uint64_t)offset - rce.start_offset;
            lseek(fd, (int64_t)((uint64_t)offset - rce.start_offset), SEEK_CUR);
            rce.offset_length -= (uint64_t)offset - rce.start_offset;
            read_offset = offset;
        }
        else if (rce.start_offset + rce.offset_length > (uint64_t)offset + rsize)
        {
            read_size = (uint64_t)offset + rsize - rce.start_offset;
            read_offset = (off_t)rce.start_offset;
        }
        else
        {
            read_size = rce.offset_length;
            read_offset = (off_t)rce.start_offset;
        }
        read_size = read_size > rsize ? rsize : read_size;
        printf("read_size ==== %ld, read_offset ==== %ld\n", read_size, read_offset);
        size_t ret = (uint64_t)read(fd, buf + (read_offset - offset), read_size);
        if (ret != read_size)
        {
            fprintf(stderr, "%d:vsphere disk read error\n", __LINE__);
            exit(1);
        }
        totle_read_size += ret;
        if (read_size < rce.offset_length)
        {
            lseek(fd, (int64_t)(rce.offset_length - read_size), SEEK_CUR);
        }
        //printf("ret = %d\n", ret);
    }
    printf("rsize=====%lu, totle_read_size=====%lu\n", rsize, totle_read_size);
    */
}


void read_vsphere_metadata_data(int fd, char *buf)
{
    struct runtime_meta_data_encoding rmde;
    memset(&rmde, 0, rmde_size);
    char *key = NULL;
    char *buffer = NULL;
    char r_buf[256];
    while (1)
    {
        if (read(fd, (char *)&rmde, rmde_size) != rmde_size)
        {
            fprintf(stderr, "vsphere disk read error\n");
            syslog(LOG_ERR, "vsphere disk read error");
            exit(1);
        }
        if (rmde.meta_key_length == 0 && rmde.meta_data_length == 0)
        {
            break;
        }
        key = (char *)malloc(rmde.meta_key_length);
        buffer = (char *)malloc(rmde.meta_data_length);
        read(fd, key, rmde.meta_key_length);
        read(fd, buffer, rmde.meta_data_length);
        sprintf(r_buf, "ddb.%s = \"%s\"\n", key, buffer);
        strcat(buf, r_buf);
        free(key);
        free(buffer);
    }
}